﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ObviousCode.Common.Utils.UI;
using ObviousCode.UI.Aspect.PluginLibrary.Interfaces;
using ObviousCode.Aspect.PluginLibrary;
using ObviousCode.Common.Utils.Library;
using ObviousCode.Common.Utils;
using ObviousCode.Common.Utils.Library.Exceptions;
using ObviousCode.Common.Utils.Library.Collections;
using ObviousCode.Aspect.PluginLibrary.Interfaces;
using ObviousCode.Common.Utils.Library.Dictionaries;
using ObviousCode.Aspect.PluginLibrary.Caches;
using ObviousCode.UI.Aspect.Persistence.AspectMenu;
using ObviousCode.UI.Aspect.Events;

namespace ObviousCode.UI.Aspect
{
    public partial class AspectMenu : UserControl, IAspectMenu
    {
		#region Fields (1) 

        Hierarchy<string> _categories;

		#endregion Fields 

		#region Constructors (1) 

        public AspectMenu()
        {
            HasDirtyData = false;

            InitializeComponent();

            SetupMenu();

            LoadEvents();
        }

		#endregion Constructors 

		#region Properties (5) 

        public List<ISurfaceObject> Collection
        {
            get
            {
                List<ISurfaceObject> collection = new List<ISurfaceObject>();

                foreach (TreeNode node in GetNodes(_menu.Nodes))
                {
                    PropertyDictionary properties = node.Tag.As<PropertyDictionary>();
                    
                    if (properties.StringFor("node-type") == "item-node")
                    {
                        ISurfaceObject surface = properties.Get<ISurfaceObject>("value");                        

                        collection.Add(surface);
                    }
                }

                return collection;
            }
        }

        public bool EnableDragDrop
        {
            get { return _menu.EnableDragDrop; }
            set { _menu.EnableDragDrop = value; }
        }

        public bool HasDirtyData { get; set; }

        public bool LabelEdit
        {
            get { return _menu.LabelEdit; }
            set { _menu.LabelEdit = value; }
        }

        public bool ShowMenuNewAsDropDown { get; set; }

		#endregion Properties 

		#region Methods (27) 

		// Public Methods (4) 

        public ISurfaceObject AddNewMenuItem(ISurfaceObjectFactory factory, string key)
        {       
            HasDirtyData = true;

            PropertyDictionary tag = GetMenuTagForNewItem(factory, key);
            
            SurfaceItemDescriptor descriptor = factory[key];

            TreeNode parent = GetParentNodeFromDescriptorKey(key);

            ISurfaceObject surface = tag.Get<ISurfaceObject>("value");
         
            IAspectItem item = surface.Tag;

            if (parent == null) ExceptionBuilder.Format("Cannot locate parent node for \"{0}\"", key);            
            
            TreeNode node = _menu.BuildNode(parent == null ? _menu.Nodes : parent.Nodes, item.Id, item.Name, descriptor.Images.TreeImageKey, tag);

            item.PropertyChanged += new PropertyChangedEventHandler(Tag_PropertyChanged);

            _menu.ExpandTo(node);

            _menu.SelectedNode = node;

            return surface;
        }

        public ISurfaceObject LoadMenuItem(ISurfaceObject surfaceObject)
        {
            IAspectItem item = surfaceObject.Tag;

            SurfaceItemDescriptor descriptor = AspectCache.Main.GetDescriptorFor(item);

            string key = descriptor.Key;

            PropertyDictionary tag = GetMenuTagForLoadedItem(surfaceObject, key);                        

            TreeNode parent = GetParentNodeFromDescriptorKey(key);            

            if (parent == null) ExceptionBuilder.Format("Cannot locate parent node for \"{0}\"", key);

            TreeNode node = _menu.BuildNode(parent == null ? _menu.Nodes : parent.Nodes, item.Id, item.Name, descriptor.Images.TreeImageKey, tag);

            item.PropertyChanged += new PropertyChangedEventHandler(Tag_PropertyChanged);

            _menu.ExpandTo(node);

            _menu.SelectedNode = node;

            return surfaceObject;
        }

        public PropertyDictionary Persist()
        {
            return MenuSaver.LatestVersion.Save(this);
        }

        public void Rebuild(Hierarchy<string> categories)
        {
            _categories = categories;

            SetupMenuForRebuild();

            _dropDownMenuButton.DropDown.Items.Clear();

            if (ShowMenuNewAsDropDown && !_dropDownMenuButton.Visible)
            {
                _dropDownMenuButton.Visible = true;
            }

            foreach (ISurfaceObjectFactory factory in AspectCache.Main.SurfaceObjectFactories)
            {
                AddFactory(factory);
            }
        }
		// Private Methods (22) 

        void _menu_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
        {
            ISurfaceObject plugin = e.Node.Tag.As<PropertyDictionary>().Get<ISurfaceObject>("value");

            IAspectItem item = plugin.Tag;

            item.Name = e.Label;
        }

        void _menu_BeforeAutoEdit(object sender, CancelEventArgs e)
        {
            e.Cancel = (_menu.SelectedNode == null || _menu.SelectedNode.Parent == null);
        }

        void _menu_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
        {
            e.CancelEdit = e.Node.Tag.As<PropertyDictionary>().StringFor("node-type") != "item-node";
        }

        void _menu_NodeCreating(object sender, TreeNodeCreatingEventArgs e)
        {
            if (NodeCreating != null)
            {
                NodeCreating(sender, e);
            }
        }

        void _menu_NodeDrag(object sender, ObviousCode.Common.Utils.UI.TreeNodeDragEventArgs e)
        {
            e.CancelDrag = (e.Node == null || e.Node.Parent == null);
        }

        void _menu_NodeDraggedOverNode(object sender, ObviousCode.Common.Utils.UI.TreeNodeDraggedOverNodeEventArgs e)
        {
            e.CanDrop = false;
        }

        private void AddFactory(ISurfaceObjectFactory factory)
        {
            foreach (string key in factory.AvailableItemKeys)
            {
                SurfaceItemDescriptor descriptor = factory[key];
                
                ToolStripItem item = _dropDownMenuButton.DropDownItems.Add(descriptor.Description, descriptor.Images.Icon, item_ClickToolStripItem);

                PropertyDictionary tag = GetMenuTagForFactory(factory, key);

                item.Tag = tag;

                if (!_menu.ImageList.Images.ContainsKey(descriptor.Images.TreeImageKey))
                {
                    _menu.ImageList.Images.Add(descriptor.Images.TreeImageKey, descriptor.Images.TreeImage);
                }



                TreeNode categoryNode = GetCategoryNodeFromDescriptorKey(key);

                TreeNode node = _menu.BuildNode(
                    categoryNode == null ? _menu.Nodes : categoryNode.Nodes, 
                    descriptor.Key, descriptor.Description + " List", descriptor.Images.TreeImageKey, tag);

                node.ContextMenu = new ContextMenu();

                MenuItem menu = new MenuItem(string.Format("New {0}", descriptor.Description), item_ClickMenuItem);
                menu.Tag = tag;

                node.ContextMenu.MenuItems.Add(menu);
            }
        }

        private void BuildTreeNodeCategories(TreeNodeCollection parentNodes, HierarchyNode<string> node)
        {                        
            TreeNode newTreeNode = _menu.BuildNode(parentNodes, node.Value, node.Value, "", GetMenuTagForCategory(node.Value));

            foreach (HierarchyNode<string> child in node.Nodes)
            {
                BuildTreeNodeCategories(newTreeNode.Nodes, child);
            }
        }

        private TreeNode GetCategoryNodeFromDescriptorKey(string descriptorKey)
        {
            string category = AspectCache.Main.GetCategoryFromDescriptorKey(descriptorKey);

            return string.IsNullOrEmpty(category) ? null : 
                
                _menu.Search(t =>                
                    (

                    t.Tag.As<PropertyDictionary>().StringFor("node-type") == "category-node" &&
                    
                    t.Tag.As<PropertyDictionary>().StringFor("value") == category
                    
                    ));

        }

        private PropertyDictionary GetMenuTagForCategory(string category)
        {
            PropertyDictionary tag = new PropertyDictionary();

            tag.Set("node-type", "category-node");                        
            tag.Set("value", category);
            tag.Set("descriptor-key", "__invalid");

            return tag;
        }

        private PropertyDictionary GetMenuTagForFactory(ISurfaceObjectFactory factory, string key)
        {
            PropertyDictionary tag = new PropertyDictionary();

            tag.Set("node-type", "factory-node");
            tag.Set("value", factory);
            tag.Set("descriptor-key", key);

            return tag;
        }

        private PropertyDictionary GetMenuTagForLoadedItem(ISurfaceObject surfaceObject, string key)
        {
            PropertyDictionary tag = new PropertyDictionary();

            tag.Set("node-type", "item-node");
            tag.Set("value", surfaceObject);
            tag.Set("descriptor-key", key);

            return tag;
        }

        private PropertyDictionary GetMenuTagForNewItem(ISurfaceObjectFactory factory, string key)
        {
            PropertyDictionary tag = new PropertyDictionary();

            tag.Set("node-type", "item-node");
            tag.Set("value", factory.BuildObject(key));
            tag.Set("descriptor-key", key);

            return tag;
        }

        private IEnumerable<TreeNode> GetNodes(TreeNodeCollection parent)
        {
            List<TreeNode> nodes = new List<TreeNode>();

            foreach (TreeNode node in parent)
            {
                nodes.Add(node);
                nodes.AddRange(GetNodes(node.Nodes));
            }

            return nodes;
        }

        private TreeNode GetParentNodeFromDescriptorKey(string descriptorKey)
        {
            return _menu.Search(
                    (t => t.Tag.As<PropertyDictionary>().StringFor("node-type") == "factory-node" &&
                    t.Tag.As<PropertyDictionary>().StringFor("descriptor-key") == descriptorKey
                    ));            
        }

        void item_ClickMenuItem(object sender, EventArgs e)
        {
            PropertyDictionary tag = StripTagFromSender(sender as Component);

            if (tag.HasKey("node-type"))
            {
                if (tag.StringFor("node-type") == "factory-node")
                {
                    ISurfaceObjectFactory factory = tag.Get<ISurfaceObjectFactory>("value");

                    AddNewMenuItem(factory, tag.StringFor("descriptor-key"));
                }
            }
        }

        void item_ClickToolStripItem(object sender, EventArgs e)
        {
            PropertyDictionary tag = sender.As<ToolStripItem>().Tag.As<PropertyDictionary>();

            if (tag.HasKey("node-type"))
            {
                if (tag.StringFor("node-type") == "factory-node")
                {
                    ISurfaceObjectFactory factory = tag.Get<ISurfaceObjectFactory>("value");

                    AddNewMenuItem(factory, tag.StringFor("descriptor-key"));
                }
            }
            
        }

        private void LoadEvents()
        {
            _menu.NodeDrag += new ObviousCode.Common.Utils.UI.NodeDragEventHandler(_menu_NodeDrag);
            _menu.NodeDraggedOverNode += new ObviousCode.Common.Utils.UI.NodeDraggedOverNodeEventHandler(_menu_NodeDraggedOverNode);
            _menu.AfterLabelEdit += new NodeLabelEditEventHandler(_menu_AfterLabelEdit);
            _menu.BeforeAutoEdit += new CancelEventHandler(_menu_BeforeAutoEdit);
        }

        private void SetupMenu()
        {
            _menu.LabelEdit = false;
            _menu.EnableDragDrop = true;
            _menu.NodeCreating += new NodeCreatingEventHandler(_menu_NodeCreating);
        }

        private void SetupMenuForRebuild()
        {
            _menu.Nodes.Clear();

            _menu.ImageList = new ImageList();

            _menu.ContextMenu = new ContextMenu();

            foreach (HierarchyNode<string> node in _categories.Nodes)
            {
                BuildTreeNodeCategories(_menu.Nodes, node);
            }
        }

        private PropertyDictionary StripTagFromSender(Component sender)
        {
            if (sender is MenuItem)
            {
                return sender.As<MenuItem>().Tag.As<PropertyDictionary>();
            }
            else if (sender is ToolStripItem)
            {
                return sender.As<ToolStripItem>().Tag.As<PropertyDictionary>();
            }
            else if (sender is Control)
            {
                return sender.As<Control>().Tag.As<PropertyDictionary>();
            }
            else throw new InvalidOperationException();
        }

        void Tag_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Name")
            {
                IAspectItem item = sender as IAspectItem;

                TreeNode[] nodes = _menu.Nodes.Find(item.Id, true);

                if (nodes.Count() == 0) return;

                nodes[0].Text = item.Name;
            }
        }
		// Internal Methods (1) 

        internal void ClearDirtyData()
        {
            HasDirtyData = false;
        }

		#endregion Methods 

        public event NodeCreatingEventHandler NodeCreating;
    }
}
